#ifndef HALIDE_ERROR_H
#define HALIDE_ERROR_H

#include <sstream>
#include <stdexcept>

#include "Debug.h"
#include "runtime/HalideRuntime.h"  // for HALIDE_ALWAYS_INLINE

namespace Halide {

/** Query whether Halide was compiled with exceptions. */
bool exceptions_enabled();

/** A base class for Halide errors.
 *
 * Note that this deliberately does *not* descend from std::runtime_error, or
 * even std::exception; unfortunately, std::runtime_error is not marked as
 * DLLEXPORT on Windows, but Error needs to be marked as such, and mismatching
 * DLLEXPORT annotations in a class inheritance hierarchy in this way can lead
 * to ODR violations. Instead, we just attempt to replicate the API of
 * runtime_error here. */
struct HALIDE_EXPORT_SYMBOL Error {
    Error() = delete;

    // Give each class a non-inlined constructor so that the type
    // doesn't get separately instantiated in each compilation unit.
    explicit Error(const char *msg);
    explicit Error(const std::string &msg);

    Error(const Error &);
    Error &operator=(const Error &);
    Error(Error &&) noexcept;
    Error &operator=(Error &&) noexcept;

    virtual ~Error();

    virtual const char *what() const noexcept;

private:
    // Using a std::string here will cause MSVC to complain about the fact
    // that class std::string isn't declared DLLEXPORT, even though the
    // field is private; rather than suppress the warning, we'll just use
    // an old-fashioned new-and-delete to keep it nice and clean.
    char *what_;
};

/** An error that occurs while running a JIT-compiled Halide pipeline. */
struct HALIDE_EXPORT_SYMBOL RuntimeError final : Error {
    static constexpr auto error_name = "Runtime error";

    explicit RuntimeError(const char *msg);
    explicit RuntimeError(const std::string &msg);
};

/** An error that occurs while compiling a Halide pipeline that Halide
 * attributes to a user error. */
struct HALIDE_EXPORT_SYMBOL CompileError final : Error {
    static constexpr auto error_name = "User error";

    explicit CompileError(const char *msg);
    explicit CompileError(const std::string &msg);
};

/** An error that occurs while compiling a Halide pipeline that Halide
 * attributes to an internal compiler bug, or to an invalid use of
 * Halide's internals. */
struct HALIDE_EXPORT_SYMBOL InternalError final : Error {
    static constexpr auto error_name = "Internal error";

    explicit InternalError(const char *msg);
    explicit InternalError(const std::string &msg);
};

/** CompileTimeErrorReporter is used at compile time (*not* runtime) when
 * an error or warning is generated by Halide. Note that error() is called
 * a fatal error has occurred, and returning to Halide may cause a crash;
 * implementations of CompileTimeErrorReporter::error() should never return.
 * (Implementations of CompileTimeErrorReporter::warning() may return but
 * may also abort(), exit(), etc.)
 */
class CompileTimeErrorReporter {
public:
    virtual ~CompileTimeErrorReporter() = default;
    virtual void warning(const char *msg) = 0;
    [[noreturn]] virtual void error(const char *msg) = 0;
};

/** The default error reporter logs to stderr, then throws an exception
 * (if HALIDE_WITH_EXCEPTIONS) or calls abort (if not). This allows customization
 * of that behavior if a more gentle response to error reporting is desired.
 * Note that error_reporter is expected to remain valid across all Halide usage;
 * it is up to the caller to ensure that this is the case (and to do any
 * cleanup necessary).
 */
void set_custom_compile_time_error_reporter(CompileTimeErrorReporter *error_reporter);

namespace Internal {

/**
 * If a custom error reporter is configured, notifies the reporter by calling
 * its error() function with the value of \p e.what()
 *
 * Otherwise, if Halide was built with exceptions, throw \p e unless an
 * existing exception is in flight. On the other hand, if Halide was built
 * without exceptions, print the error message to stderr and abort().
 *
 * @param e The error to throw or report
 */
/// @{
[[noreturn]] void throw_error(const RuntimeError &e);
[[noreturn]] void throw_error(const CompileError &e);
[[noreturn]] void throw_error(const InternalError &e);
/// @}

/**
 * If a custom error reporter is configured, notifies the reporter by calling
 * its warning() function. Otherwise, prints the warning to stderr.
 *
 * @param warning The warning to issue
 */
void issue_warning(const char *warning);

template<typename T>
struct ReportBase {
    std::ostringstream msg;

    ReportBase(const char *file, const char *function, int line, const char *condition_string, const char *prefix) {
        if (debug_is_active_impl(1, file, function, line)) {
            msg << prefix << " at " << file << ":" << line << ' ';
            if (condition_string) {
                msg << "Condition failed: " << condition_string << ' ';
            }
        }
    }

    // Just a trick used to convert RValue into LValue
    HALIDE_ALWAYS_INLINE T &ref() {
        return *static_cast<T *>(this);
    }

    template<typename S>
    HALIDE_ALWAYS_INLINE T &operator<<(const S &x) {
        msg << x;
        return *static_cast<T *>(this);
    }

protected:
    std::string finalize_message() {
        if (!msg.str().empty() && msg.str().back() != '\n') {
            msg << "\n";
        }
        return msg.str();
    }
};

template<typename Exception>
struct ErrorReport : ReportBase<ErrorReport<Exception>> {
    using Base = ReportBase<ErrorReport>;

    ErrorReport(const char *file, const char *function, int line, const char *condition_string)
        : Base(file, function, line, condition_string, Exception::error_name) {
        this->msg << "Error: ";
    }

    /** When you're done using << on the object, and let it fall out of
     * scope, this errors out, or throws an exception if they are
     * enabled. This is a little dangerous because the destructor will
     * also be called if there's an exception in flight due to an
     * error in one of the arguments passed to operator<<. We handle
     * this by only actually throwing if there isn't an exception in
     * flight already.
     */
    [[noreturn]] ~ErrorReport() noexcept(false) {
        throw_error(Exception(this->finalize_message()));
    }
};

struct WarningReport : ReportBase<WarningReport> {
    WarningReport(const char *file, const char *function, int line, const char *condition_string)
        : ReportBase(file, function, line, condition_string, "Warning") {
        this->msg << "Warning: ";
    }

    /** When you're done using << on the object, and let it fall out of
     * scope, this prints the computed warning message.
     */
    ~WarningReport() {
        issue_warning(this->finalize_message().c_str());
    }
};

// This uses operator precedence as a trick to avoid argument evaluation if
// an assertion is true: it is intended to be used as part of the
// _halide_internal_assertion macro, to coerce the result of the stream
// expression to void (to match the condition-is-false case).
struct Voidifier {
    // This has to be an operator with a precedence lower than << but
    // higher than ?:
    template<typename T>
    HALIDE_ALWAYS_INLINE void operator&(T &) {
    }
};

/**
 * _halide_internal_assertion is used to implement our assertion macros
 * in such a way that the messages output for the assertion are only
 * evaluated if the assertion's value is false.
 *
 * Note that this macro intentionally has no parens internally; in actual
 * use, the implicit grouping will end up being
 *
 *   condition ? (void) : (Voidifier() & (ErrorReport << arg1 << arg2 ... << argN))
 *
 * This (regrettably) requires a macro to work, but has the highly desirable
 * effect that all assertion parameters are totally skipped (not ever evaluated)
 * when the assertion is true.
 */
#define _halide_internal_assertion(condition, type)  \
    /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \
    (condition) ? (void)0 : ::Halide::Internal::Voidifier() & ::Halide::Internal::ErrorReport<type>(__FILE__, __FUNCTION__, __LINE__, #condition).ref()

#define internal_error Halide::Internal::ErrorReport<Halide::InternalError>(__FILE__, __FUNCTION__, __LINE__, nullptr)
#define user_error Halide::Internal::ErrorReport<Halide::CompileError>(__FILE__, __FUNCTION__, __LINE__, nullptr)
#define user_warning Halide::Internal::WarningReport(__FILE__, __FUNCTION__, __LINE__, nullptr)
#define halide_runtime_error Halide::Internal::ErrorReport<Halide::RuntimeError>(__FILE__, __FUNCTION__, __LINE__, nullptr)

#define internal_assert(c) _halide_internal_assertion(c, Halide::InternalError)
#define user_assert(c) _halide_internal_assertion(c, Halide::CompileError)

// The nicely named versions get cleaned up at the end of Halide.h,
// but user code might want to do halide-style user_asserts (e.g. the
// Extern macros introduce calls to user_assert), so for that purpose
// we define an equivalent macro that can be used outside of Halide.h
#define _halide_internal_assert(c) _halide_internal_assertion(c, Halide::InternalError)
#define _halide_user_assert(c) _halide_internal_assertion(c, Halide::CompileError)

// N.B. Any function that might throw a user_assert or user_error may
// not be inlined into the user's code, or the line number will be
// misattributed to Halide.h. Either make such functions internal to
// libHalide, or mark them as HALIDE_NO_USER_CODE_INLINE.

// handler suitable for use with std::terminate; it will catch unhandled exceptions
// and log the `what()` to stderr, then abort. Exposed as a function to minimize
// the need for external code to need to know the definition of Halide::Error.
HALIDE_EXPORT_SYMBOL void unhandled_exception_handler();

}  // namespace Internal

}  // namespace Halide

#endif
